#ifndef MENU_H
#define MENU_H

#include <stdio.h>
#include <QPoint>
#include <QGradient>
#include <QRadialGradient>
#include <QFontMetrics>

#include "utils.h"
#include "../gui_base.h"
#include "math.h"

#include <string>

class Menu;

class MenuListener
{
public:
    virtual void OnClick(Menu* m, QMouseEvent* event) = 0;
};

class Menu: public GuiBase, public AnimationObserver, public Observable< MenuListener >
{
public:
    Menu(Engine* engine, int _id, int _x, int _y, QString _text, bool _submenu = false)
        :GuiBase(engine, _x, _y), intro(engine->fps, 0), select(engine->fps, 0),
         p_ni(0.7), om_ni(&p_ni, false), penColor(0, 0, 0, 0),  brushColor(0, 0, 0, 0),
         menuFont("Palatino", 11), text(_text), id(_id)
    {
        submenu = _submenu;
        mouseTracking = true;
        line_length = penAlpha = sq_dist = brushGreen = brushBlue = rect_w = brushRed = 0;

        // adds properties to the graph
        int* vals[7] = {&line_length, &penAlpha, &sq_dist, &brushGreen, &brushBlue, &brushRed, &rect_w};
        string names[7] = {"ll", "alpha", "dist", "bGreen", "bBlue", "bRed", "rect_w"};
        graph.AddSimpleProperties(names, vals, 7, &invalidated);

        // intro/outro
        prevGreen = 0;
        intro.AddObserver(this);
        graph.AddInterpolator(intro.GetTime(), 0, 1000, "alpha", 0, 255);
        graph.AddInterpolator("alpha", 0, 255, "ll", 0, 100);
        graph.AddInterpolator("ll", 0, 100, "bBlue", 0, 255);
        green_approach_id = graph.AddInterpolator(intro.GetTime(), 0, 1000, "bGreen", 0, 0);

        // other stuff and drawing constants
        QFontMetrics fm(menuFont);
        textWidth = fm.width(text);

        // approach
        vLineLength = hLineLength = 0;
        string names2[2] = {"vll", "hll"};
        int* vals2[2] = {&vLineLength, &hLineLength};
        graph.AddSimpleProperties(names2, vals2, 2, &invalidated);
        graph.AddInterpolator("dist", 100, 15000, "bBlue", 0, 255, &p_ni, submenu);
        graph.AddInterpolator("dist", 100, 15000, "bGreen", 0, 255, &om_ni, submenu);
        graph.AddInterpolator("dist", 100, 15000, "rect_w", 0, 85, &om_ni, submenu);
        graph.AddInterpolator("dist", 10000, 15000, "vll", 0, 20, &om_ni, !submenu);
        graph.AddInterpolator("dist", 100, 10000, "hll", 0, textWidth + 5, &om_ni, !submenu);
        p_sqd = graph.Get("dist");

        // select/unselect
        select.AddObserver(this);
        graph.AddInterpolator(select.GetTime(), 0, 0, "bGreen", 0, 255);
        i_id = graph.AddInterpolator("bGreen", 0, 255, "rect_w", 0, 85);
        graph.AddInterpolator("bGreen", 0, 255, "bBlue", 0, 255, &om_ni);

        // redraw window
        sx = x; sy = y; sw = 97; sh = 40;
        w = 100; h = 25;

        // interpolable properties
        xSetter = new XYSetter(&(GuiBase::x), &sx);
        ySetter = new XYSetter(&(GuiBase::y), &sy);
        xGetter = new SimplePropertyGetter<int>(&(GuiBase::x));
        yGetter = new SimplePropertyGetter<int>(&(GuiBase::y));
        Interpolable::RegisterProperty<int>(this, "x", xGetter, xSetter, &invalidated);
        Interpolable::RegisterProperty<int>(this, "y", yGetter, ySetter, &invalidated);

        backBrush = new QBrush(QColor(0, 0, 255, 170));
        center = new QPoint(x + 80, y + 10);
        mousePressed = false;
        mouseClick = false;
        visible = false;
        selected = false;
        enabled = true;
    }

    ~Menu()
    {
        delete xGetter;
        delete xSetter;
        delete yGetter;
        delete ySetter;
        delete backBrush;
        delete center;
    }

    void SetVisible(bool set, int animeDuration = -1)
    {
        if(visible == set) return;
        visible = set;

        if(animeDuration != -1)
        {
            graph.Get(green_approach_id)->SetMaxY(prevGreen);
            intro.SetDuration(animeDuration);
            intro.Reverse(!set);
            engine->AddAnimation(&intro);
            intro.Play();
        }else
        {
            prevGreen = brushGreen;
            intro.Stop();
        }
    }

    void SetSelected(bool set, int animeDuration = 100)
    {
        if(!visible) return;
        if(selected == set) return;
        selected = set;
        (*p_sqd)(100);
        mouseTracking = !selected;
        select.SetDuration(animeDuration);
        engine->AddAnimation(&select);

        if(selected) // we must select the menu
        {
            graph.Get(i_id)->SetMinY(rect_w);
            select.Stop();
            select.Play();
        } else
        {
            select.Reverse(true);
            select.Play();
        }
    }

    void SetEnabled(bool set)
    {
        if(enabled == set) return;
        enabled = set;
        invalidated = true;
    }

    bool IsVisible(){ return visible; }
    bool IsSelected(){ return selected; }
    bool IsSubmenu(){ return submenu; }
    int GetId(){ return id; }
    int GetX(){ return x; }
    int GetY(){ return y; }
    void SetY(int newY)
    {
        y = newY;
        sy = newY;
    }

    void SetX(int newX)
    {
        x = newX;
        sx = newX;
    }

    QString GetText(){ return text; }

    // must set sx, sy, sw, sh
    void Draw(QPainter& painter)
    {
        if(!visible) return;

        penColor.setAlpha(penAlpha);
        brushColor.setAlpha(penAlpha/2);
        brushColor.setGreen(brushGreen);
        brushColor.setBlue(brushBlue);
        brushColor.setRed(brushRed);

        painter.setPen(penColor);
        painter.setFont(menuFont);

        if(submenu)
        {
            int k = x + 90 - textWidth;
            if(text != "Exit")
            {
                painter.drawLine(x, y + 20, x, y - 10);
            }else
            {
                center->setX(x + 80);
                center->setY(y + 10);
            }
            painter.drawLine(k, y + 20, k, y + 20 - vLineLength);
            if(hLineLength>2)
            {
                painter.drawLine(k, y, k + hLineLength, y);
            }
        }else
        {
            painter.setBrush(brushColor);
            center->setX(x + 80);
            center->setY(y + 10);
            painter.drawRect(x, y, 20 + rect_w, 20);
        }
        painter.drawLine(x, y + 20, x + line_length, y + 20);

        if(!enabled)
        {
            painter.setPen(Qt::gray);
        }
        painter.drawText(x + 95 - textWidth, y + 17, text);
        //invalidated = false;
    }

    void MouseMoved(QMouseEvent *event)
    {
        if(!enabled) return;
        int squaredDist = pow(event->x() - center->x(), 2) + pow(event->y() - center->y(), 2);
        (*p_sqd)(squaredDist);
    }

    virtual void MousePressed(QMouseEvent *event)
    {
        if(!visible || !enabled) return;
        int x_pos = event->x(), y_pos = event->y();
        mousePressed = ( x_pos >= x && x_pos <= x + w && y_pos >= y && y_pos <= y + h);
    }

    virtual void MouseReleased(QMouseEvent *event)
    {
        if(!visible || !enabled) return;
        int x_pos = event->x(), y_pos = event->y();
        mouseClick = mousePressed && ( x_pos >= x && x_pos <= x + w && y_pos >= y && y_pos <= y + h);
        if(mouseClick)
        {
            SetSelected(!selected);
            for( vector< MenuListener* >::iterator i = observers->begin(); i!=observers->end(); ++i)
            {
                (*i)->OnClick(this, event);
            }
        }
    }

    virtual void OnPause(Animation* anime){};
    virtual void OnStop(Animation* anime){};
    virtual void OnEnd(Animation* anime)
    {
        engine->RemoveAnimation(anime);
    };
    virtual void OnPlay(Animation* anime){};

private:
    class XYSetter: public PropertySetter<int>
    {
    public:
        XYSetter(int* _p, int* _sp):p(_p), sp(_sp){}

        virtual void operator()(const int& val)
        {
            *p = val;
            *sp = val;
        }

    private:
        int* p;
        int* sp;
    };

    // animations and approach
    Animation intro;
    Animation select;

    int line_length, rect_w, penAlpha, brushBlue, brushGreen, brushRed, sq_dist;
    IntGraph graph;
    Power_NI<float> p_ni;
    OneMinus_NI<float> om_ni;
    Property<int>* p_sqd;
    int i_id;
    int green_approach_id;
    int prevGreen;

    // submenu approaching
    int vLineLength;
    int hLineLength;

    // interpolable stuff
    XYSetter* xSetter;
    XYSetter* ySetter;
    SimplePropertyGetter<int>* xGetter;
    SimplePropertyGetter<int>* yGetter;

    // some stuff
    QColor penColor;
    QColor brushColor;
    QFont menuFont;
    QPoint* center;
    QBrush* backBrush;

    // state flags
    bool mousePressed;
    bool mouseClick;
    bool visible;
    bool selected;
    bool submenu;
    bool enabled;

    // model fields
    QString text;
    int textWidth;
    int id;
};

#endif // MENU_H
